home *** CD-ROM | disk | FTP | other *** search
/ CD ROM Paradise Collection 4 / CD ROM Paradise Collection 4 1995 Nov.iso / program / swagd_f.zip / FAQ.SWG / 0012_BASM Tutoral #1.pas < prev    next >
Pascal/Delphi Source File  |  1993-06-08  |  17KB  |  393 lines

  1. ===========================================================================
  2.  BBS: The Beta Connection
  3. Date: 06-03-93 (00:08)             Number: 680
  4. From: CHRIS PRIEDE                 Refer#: NONE
  5.   To: ALL                           Recvd: NO  
  6. Subj: BASM TUT01 (1/4)               Conf: (232) T_Pascal_R
  7. ---------------------------------------------------------------------------
  8.  
  9.     No matter what HLL you use -- Pascal, C, COBOL or some other
  10. language -- there is still place for assembly in your programs.
  11. Corresponding directly to the native language of your computer, it
  12. combines unlimited control with unmatched efficiency.
  13.  
  14.     Since Borland added the built-in assembler in TP 6.0, enhancing
  15. Pascal programs with some assembly has become much easier than before.
  16. However, I have read many books and found the BASM sections in Turbo
  17. Pascal books are inadequate and frequently contain errors. There are
  18. very good assembly books available, but they focus on writing
  19. assembly-only programs using standalone assemblers.
  20.  
  21.     Considering this, I decided to write a text file -- assembly
  22. language lessons for TP programmers, mostly using BASM. When I asked our
  23. host Guy for his opinion on this idea, he aproved and suggested I post
  24. sections weekly in this conference.
  25.  
  26.     A large part of this tutorial will be dedicated not to assembly
  27. language itself, but tasks that often require it: writing interrupt
  28. handlers and TSRs, accessing hardware directly. I will try to post new
  29. sections weekly, if my schedule permits. Questions, suggestions and
  30. criticism are very welcome.
  31.  
  32.     You will need a copy of TP 6.0 or later. Turbo Debugger, TASM or
  33. MASM could be useful, but are not required.
  34.  
  35.  
  36.  - I -
  37.  
  38.     To get started, we will take a Pascal routine, convert it to
  39. assembly (using BASM) and see if we can beat the compiler at code
  40. generation. We will use a simple integer square root function; it is not
  41. something you would need often (unless you are writing graphics
  42. routines), but serves well as an example and only requires simple data
  43. movement and arithmetic instructions.
  44.  
  45. function ISqr(I: word): word;
  46. var Root, LRoot: word;
  47. begin
  48.   Root := 1;
  49.   repeat
  50.     LRoot := Root;
  51.     Root := ((I div Root) + Root) div 2;
  52.   until ((integer(Root - LRoot) <= 1) and
  53.    (integer(Root - LRoot) >= -1));
  54.   ISqr := Root;
  55. end;
  56.  
  57.     It is based on a well-known formula (name has escaped my memory).
  58. The loop usually continues until Root and LRoot are equal, but that
  59. might never happen with integers because fraction is truncated. Our
  60. version loops until difference is less than 1, resulting in almost
  61. correctly rounded result. The number of iterations required to find
  62. square root of N never exceeds (ln N) +1, which means our function will
  63. find the square root of any valid argument in 12 or less iterations.
  64.  
  65.     Now, let's convert this to assembly. One major improvement we can
  66. make is to place both temporary variables in registers. CPU can access
  67. registers much faster than memory. AX and DX are needed for division, so
  68. we will assign Root to register CX and LRoot -- to BX:
  69.  
  70. function ISqr(I: word): word; assembler;
  71. asm
  72.   mov   cx, 1           {  Root := 1                                  }
  73. @@1:                    { loop start label                            }
  74.   mov   bx, cx          {  LRoot := Root                              }
  75.   mov   ax, I           {<                    <                       }
  76.   sub   dx, dx          {< AX := (I div Root) <                       }
  77.   div   cx              {<                    < Root := ((I div Root) }
  78.   add   ax, cx          {  AX := AX + Root    <   + Root) div 2       }
  79.   shr   ax, 1           {  AX := AX div 2     <                       }
  80.   mov   cx, ax          {  Root := AX         <                       }
  81.   sub   ax, bx          {  AX := Root - LRoot                         }
  82.   cmp   ax, 1           {  Compare AX to 1...                         }
  83.   jg    @@1             {  Greater than 1  -- continue loop           }
  84.   cmp   ax, -1          {  Compare AX to -1...                        }
  85.   jl    @@1             {  Less than -1 -- continue loop              }
  86.   mov   ax, cx          {  ISqr := Root -- return result in AX        }
  87. end;
  88.  
  89.     Simple statements translate to one instruction, while complex
  90. expressions have to be broken down in smaller steps. Notice we compute
  91. expression (Root - LRoot) only once, although it's result is tested
  92. twice. As you will see shortly, Turbo Pascal is not smart enough yet to
  93. take advantage of this: compiled Pascal code would subtract twice.
  94.  
  95. (continued in next message...)
  96. ---
  97.  * D.W.'s TOOLBOX, Atlanta GA, 404-471-6636
  98.  * PostLink(tm) v1.05  DWTOOLBOX (#1035) : RelayNet(tm)
  99. ===========================================================================
  100.  BBS: The Beta Connection
  101. Date: 06-03-93 (00:10)             Number: 681
  102. From: CHRIS PRIEDE                 Refer#: NONE
  103.   To: ALL                           Recvd: NO  
  104. Subj: BASM TUT01 (2/4)               Conf: (232) T_Pascal_R
  105. ---------------------------------------------------------------------------
  106.     Function result is left in AX register. TP expects function return
  107. values in the following registers:
  108.  
  109.     Char, Byte          : AL
  110.     Word, Integer       : AX
  111.     LongInt, pointers   : DX:AX
  112.     (low order word/offset in AX, high order word/segment in DX)
  113.  
  114.  
  115.     Let's go through this line by line...
  116.  
  117. * function ISqr(I: word): word; assembler;
  118.  
  119.     This is a standard function declaration, except for the word
  120. "assembler", which tells Turbo Pascal entire function is going to be in
  121. assembly (if you try to insert some Pascal code in assembler function,
  122. it won't work).
  123.  
  124. * asm
  125.  
  126.     "Asm" means start of assembly block, just like "begin" means start
  127. of Pascal block. Since our function was declared assembler, it has only
  128. asm block; without "assembler" keyword it would have to start with
  129. "begin":
  130.  
  131. function Foo;
  132. begin
  133.   asm
  134.     { some assembly code }
  135.   end;
  136. end;
  137.  
  138.     You can use asm blocks anywhere in your Pascal code, but conventions
  139. for pure asm functions are somewhat different.
  140.  
  141. *  mov   cx, 1
  142.  
  143.     MOV (Move) instruction is assembly assignment statement. This line
  144. is equivalent to CX := 1 in Pascal.
  145.  
  146. * @@1:
  147.  
  148.     This is a local label, not unlike Pascal labels, used with dreaded
  149. GOTO statements. Unfortunately, the only means of flow control in asm is
  150. using GOTO-like jumps (conditional or unconditional), so you would
  151. better get used to it. Destination of such jumps can be a Pascal-style,
  152. previously declared label or a local label, like the one above. Local
  153. labels don't have to be previously declared, but they should start with
  154. @ (at sign).
  155.  
  156. *  mov   bx, cx
  157. *  mov   ax, I
  158.  
  159.     Some more MOVing. Notice we can move data between two registers or
  160. register and memory (argument I is stored on stack -- in memory). We
  161. can't, however, directly move one memory variable into another: most
  162. 80x86 instructions can't have two memory operands. If you ever need to
  163. assign one memory variable to another, it should be done through a
  164. register, like this:
  165.  
  166.     mov     ax, X
  167.     mov     Y, ax
  168.  
  169.     The same applies to most other instructions with two operands.
  170.  
  171. *  sub   dx, dx
  172.  
  173.     SUB (Subtract) subtracts the right operand from left and leaves the
  174. result in left operand. SUB AX, CX is equivalent to AX := AX - CX in
  175. Pascal.
  176.  
  177.     As you may have noticed, we are subtracting DX from itself. This is
  178. a better way of setting register to 0 (100 - 100 = 0). We could use MOV
  179. DX, 0, but SUB instruction is one byte smaller and a few clock cycles
  180. faster. Some programmers use XOR for the same purpose: a number XORed
  181. with itself results in 0 too. We need DX to be 0 for division.
  182.  
  183. *  div   cx
  184.  
  185.     DIV (Divide). 80x86 divide and multiply instructions are different
  186. from other arithmetic instructions. You only need to specify divisor;
  187. other operands are assumed to be AL, AH or AX, DX registers. This table
  188. summarizes both DIV variants:
  189.  
  190. Dividend       │Divisor    │Quotient       │Remainder
  191. ───────────────┼───────────┼───────────────┼
  192. 16 bit (AX)    │8 bit      │8 bit (AL)     │8 bit (AH)
  193. 32 bit (DX:AX) │16 bit     │16 bit (AX)    │16 bit (DX)
  194.  
  195. (continued in next message...)
  196. ---
  197.  * D.W.'s TOOLBOX, Atlanta GA, 404-471-6636
  198.  * PostLink(tm) v1.05  DWTOOLBOX (#1035) : RelayNet(tm)
  199. ===========================================================================
  200.  BBS: The Beta Connection
  201. Date: 06-03-93 (00:12)             Number: 682
  202. From: CHRIS PRIEDE                 Refer#: NONE
  203.   To: ALL                           Recvd: NO  
  204. Subj: BASM TUT01 (3/4)               Conf: (232) T_Pascal_R
  205. ---------------------------------------------------------------------------
  206.     The size of divisor selects between 16 and 32 bit divide. In our
  207. function both dividend and divisor are 16 bit words, which is why we had
  208. to zero DX, effectively extending word in AX to 32 bits.
  209.  
  210.     Divisor should be a register or memory variable. If you need to
  211. divide by immediate value, first move it in a register:
  212.  
  213.     mov     bx, 320
  214.     div     bx
  215.  
  216.     Use IDIV (Integer Divide) for signed numbers (integer, longint).
  217. IDIV works exactly like DIV, but performs signed division.
  218.  
  219. * add   ax, cx
  220.  
  221.     ADD (Addition). Pascal equivalent: AX := AX + CX.
  222.  
  223. * shr   ax, 1
  224.  
  225.     Bitwise Shift Right. Pascal equivalent: AX := AX shr 1. As the name
  226. implies, this instruction shifts bits right:
  227.  
  228. AX before shift:    0000101100110110    (decimal 2870)
  229. AX after shift:     0000010110011011    (decimal 1435)
  230.  
  231.     If you look at the decimal values on the right, you will notice
  232. shifting divided the number by 2. That is correct: shifting a binary
  233. number N bits left/right is equivalent to multiplying/dividing it by
  234. 2^N. CPU can shift bits much faster than divide and shift instructions
  235. are not restricted to certain register(s) like DIV -- remember this
  236. when you need to multiply/divide by a power of 2.
  237.  
  238.     The first operand (value to be shifted) can be either register or
  239. memory. The second operand is number of bits to shift -- immediate value
  240. or CL register. 8086 allows _only_ immediate value of 1; to shift by more
  241. than one bit use the following:
  242.  
  243.     mov     cl, 3   (bit count)
  244.     shr     ax, cl
  245.  
  246.     You can also shift several times by one (I would use this method only
  247. to shift by 2 - 3 bits, otherwise it gets too long):
  248.  
  249.     shr     ax, 1
  250.     shr     ax, 1
  251.     shr     ax, 1
  252.  
  253.     286+ can shift by any immediate count. If you are compiling for 286
  254. and better ({$G+} compiler directive), you can do this:
  255.  
  256.     shr     ax, 3
  257.  
  258. *  cmp   ax, 1
  259.  
  260.     CMP (Compare) compares two operands and sets CPU flags to reflect
  261. their relationship. Flag state are later used to decide if a conditional
  262. jump instruction should jump or not. This two step process is used to
  263. control program flow, like if..then statements and loops in Pascal.
  264.  
  265. *  jg    @@1
  266.  
  267.     ...and this is a conditional jump. JG (Jump if Greater) will transfer
  268. control (GOTO) to label @@1 if the last compare found left operand to be
  269. greater than right, otherwise it "falls through": execution continues at
  270. the next instruction. JG assumes operands were signed (integers). Use JA
  271. (Jump if Above) for unsigned values (words). The following is a summary
  272. of conditional jumps for arithmetic relationships:
  273.  
  274. JA/JNBE     Jump if Above                   (">",  unsigned)
  275. JG/JNLE     Jump if Greater                 (">",  signed)
  276. JAE/JNB     Jump if Above or Equal          (">=", unsigned)
  277. JGE/JNL     Jump if Greater or Equal        (">=", signed)
  278. JE/JZ       Jump if Equal                   ("=")
  279. JNE/JNZ     Jump if Not Equal               ("<>")
  280. JB/JNAE     Jump if Below                   ("<",  usigned)
  281. JL/JNGE     Jump if Less                    ("<",  signed)
  282. JBE/JNA     Jump if Below or Equal          ("<=", unsigned)
  283. JLE/JNG     Jump if Less or Equal           ("<=", signed)
  284.  
  285.     For ease of use, assemblers recognize two different mnemonics for
  286. most conditional jumps. Use the one you find less cryptic.
  287.  
  288.     Since conditional jump instructions simply inspect flags set by
  289. previous compare, there may be other instructions in between, provided
  290. they don't alter flags -- for example, MOV. Flags are not cleared and
  291. can be tested more than once. For example, you could do this:
  292.  
  293. (continued in next message...)
  294. ---
  295.  * D.W.'s TOOLBOX, Atlanta GA, 404-471-6636
  296.  * PostLink(tm) v1.05  DWTOOLBOX (#1035) : RelayNet(tm)
  297. ===========================================================================
  298.  BBS: The Beta Connection
  299. Date: 06-03-93 (00:29)             Number: 683
  300. From: CHRIS PRIEDE                 Refer#: NONE
  301.   To: ALL                           Recvd: NO  
  302. Subj: BASM TUT01 (4/4)               Conf: (232) T_Pascal_R
  303. ---------------------------------------------------------------------------
  304.     mov     ax, X
  305.     cmp     ax, Y
  306.     jg      @XGreater   { X > Y }
  307.     jl      @XLess      { X < Y }
  308.     ....                { fell through -- X = Y }
  309.  
  310. *   end;
  311.  
  312.     Like Pascal blocks, this means the end of BASM block. If you were
  313. using a standalone assembler, you would have to add RET (Return from
  314. subroutine) instruction and possibly some other (cleanup) code. BASM
  315. adds this and similar purpose code at the top of the function
  316. automatically -- it is called entry & exit code and usually amounts
  317. to 1 - 3 instructions, depending from number of arguments and local
  318. variables.
  319.  
  320.     Well, looks like everything is covered... Quite a bit for the first
  321. lesson too. If you have the instruction set reference, check it for more
  322. detailed descriptions. If you don't, I strongly recommend to obtain one.
  323. This is just one of many sources:
  324.  
  325.     The Waite Group's "Microsoft Macro Assembler Bible" or "Turbo
  326. Assembler Bible", ISBN 0-672-22659-6 (MASM flavor). $29.95, published
  327. by SAMS. A very good reference book, comes in MASM and TASM flavors,
  328. you choose.
  329.  
  330.     Finally, here is disassembly of compiled TP code. [BP+n] means
  331. reference to function argument, [BP-n] -- to local variable. This is
  332. provided mainly to satisfy your curiosity, but it shows we thought very
  333. much like the compiler, only coded it better:
  334.  
  335. ISQR:
  336. isqr1.pas#9:begin
  337.    0000:0000 55              PUSH    BP
  338.    0000:0001 89E5            MOV     BP,SP
  339.    0000:0003 83EC06          SUB     SP,+06
  340. isqr1.pas#10:  Root := 1;
  341.    0000:0006 C746FC0100      MOV     [WORD BP-04],0001
  342. isqr1.pas#11:  repeat
  343. isqr1.pas#12:    LRoot := Root;
  344.    0000:000B 8B46FC          MOV     AX,[BP-04]
  345.    0000:000E 8946FA          MOV     [BP-06],AX
  346. isqr1.pas#13:    Root := ((I div Root) + Root) div 2;
  347.    0000:0011 8B4604          MOV     AX,[BP+04]
  348.    0000:0014 31D2            XOR     DX,DX
  349.    0000:0016 F776FC          DIV     [BP-04]
  350.    0000:0019 0346FC          ADD     AX,[BP-04]
  351.    0000:001C D1E8            SHR     AX,1
  352.    0000:001E 8946FC          MOV     [BP-04],AX
  353. isqr1.pas#14:  until ((integer(Root - LRoot) <= 1) and
  354. isqr1.pas#15:      (integer(Root - LRoot) >= -1));
  355.    0000:0021 8B46FC          MOV     AX,[BP-04]
  356.    0000:0024 2B46FA          SUB     AX,[BP-06]
  357.    0000:0027 3D0100          CMP     AX,0001
  358.    0000:002A 7FDF            JG      isqr1.pas#12(000B)
  359.    0000:002C 8B46FC          MOV     AX,[BP-04]
  360.    0000:002F 2B46FA          SUB     AX,[BP-06]
  361.    0000:0032 3DFFFF          CMP     AX,0FFFF  (-1)
  362.    0000:0035 7CD4            JL      isqr1.pas#12(000B)
  363. isqr1.pas#16:  ISqr := Root;
  364.    0000:0037 8B46FC          MOV     AX,[BP-04]
  365.    0000:003A 8946FE          MOV     [BP-02],AX
  366. isqr1.pas#17:end;
  367.    0000:003D 8B46FE          MOV     AX,[BP-02]
  368.    0000:0040 89EC            MOV     SP,BP
  369.    0000:0042 5D              POP     BP
  370.    0000:0043 C20200          RETN    0002
  371.  
  372.     TP's code doesn't place variables in registers and does some
  373. unnecessary work (ie, subtracts Root - Lroot twice; see above). Entry
  374. and exit code is shown too.
  375.  
  376.     Our version is slightly faster, but I didn't pick this routine to
  377. demonstrate optimization -- integer math is about the only area where
  378. most compilers do well. A good optimizing compiler should be able to
  379. generate code as good as ours. I would be curious to see what this
  380. looks like compiled with SBP+ (Guy: consider this a strong hint :)).
  381. Most code you would normally write in assembly will show much greater
  382. improvement (string routines, etc.).
  383.  
  384.     What to expect: next time we will discuss how to call DOS or BIOS
  385. interrupts using BASM; what registers are available; which have special
  386. uses or should be preserved; how to access strings, arrays and records.
  387. Somewhere in near future: writing object methods in BASM.
  388.  
  389.                              *  *  *
  390. ---
  391.  * D.W.'s TOOLBOX, Atlanta GA, 404-471-6636
  392.  * PostLink(tm) v1.05  DWTOOLBOX (#1035) : RelayNet(tm)
  393.